import ensemblerobot
import midi
import random

Defaults = {
    'WireMap':[
        # 1
        (None, midi.G_4),
        # 2
        (midi.Fs_4, midi.Gs_4), 
        # 3
        (midi.F_4, midi.A_4), 
        # 4
        (midi.E_4, midi.As_4),
        # 5
        (midi.Ds_4, midi.B_4),
        # 6
        (midi.D_4, midi.C_5),
        # 7
        (midi.Cs_4, midi.Cs_5),
        # 8
        (midi.C_4, midi.D_5),
        # 9
        (midi.B_3, midi.Ds_5),
        # 10
        (midi.As_3, midi.E_5),
        # 11
        (midi.A_3, midi.F_5),
        # 12
        (midi.Gs_3, midi.Fs_5),
        # 13
        (midi.G_3, midi.G_5),
    ],
    'SolenoidWireMap':[
        # 1
        (None, 34),
        # 2
        (36, 38),
        # 3
        (40, 42),
        # 4
        (46, 44),
        # 5
        (48, 50),
        # 6
        (52, 54),
        # 7
        (56, 58),
        # 8
        (60, 62),
        # 9
        (90, 88),
        # 10
        (94, 92),
        # 11
        (82, 80),
        # 12
        (86, 84),
        # 13
        (74, 72),
    ],
    'SolenoidChannel':0,
    'LEDWireMap':[
        # 1
        (None, 70),
        # 2
        (69, 68),
        # 3
        (67, 66),
        # 4
        (64, 65),
        # 5
        (87, 86),
        # 6
        (85, 84),
        # 7
        (74, 73),
        # 8
        (72, 79),
        # 9
        (77, 78),
        # 10
        (75, 76),
        # 11
        (81, 80),
        # 12
        (83, 82),
        # 13
        (94, 95),
    ],
    'LEDChannel':1,
    'delaymap':{
        midi.G_3: (0.050000, 0.075000),
        midi.Gs_3: (0.050000, 0.075000),
        midi.A_3: (0.050000, 0.075000),
        midi.As_3: (0.050000, 0.075000),
        midi.B_3: (0.050000, 0.075000),
        midi.C_4: (0.050000, 0.075000),
        midi.Cs_4: (0.050000, 0.075000),
        midi.D_4: (0.050000, 0.075000),
        midi.Ds_4: (0.050000, 0.075000),
        midi.E_4: (0.050000, 0.075000),
        midi.F_4: (0.050000, 0.075000),
        midi.Fs_4: (0.050000, 0.075000),
        midi.G_4: (0.050000, 0.075000),
        midi.Gs_4: (0.050000, 0.075000),
        midi.A_4: (0.050000, 0.075000),
        midi.As_4: (0.050000, 0.075000),
        midi.B_4: (0.050000, 0.075000),
        midi.C_5: (0.050000, 0.075000),
        midi.Cs_5: (0.050000, 0.075000),
        midi.D_5: (0.050000, 0.075000),
        midi.Ds_5: (0.050000, 0.075000),
        midi.E_5: (0.050000, 0.075000),
        midi.F_5: (0.050000, 0.075000),
        midi.Fs_5: (0.050000, 0.075000),
        midi.G_5: (0.050000, 0.075000),
    }
}

class Heliphon(ensemblerobot.EnsembleRobot):
    _defaults = Defaults

    def init(self):
        self.led_map = []
        self.solenoid_map = []
        self._makemaps()

    def _makemaps(self):
        self.led_map = range(128)
        self.solenoid_map = range(128)
        maps = zip(self.WireMap, self.SolenoidWireMap, self.LEDWireMap)
        for w, s, l in maps:
            left_w, right_w = w
            left_s, right_s = s
            left_l, right_l = l
            if left_w:
                self.solenoid_map[left_w] = left_s
                self.led_map[left_w] = left_l
            if right_w:
                self.solenoid_map[right_w] = right_s
                self.led_map[right_w] = right_l

    ## Delay
    def calculate_msdelay(self, velocity):
        try:
            low, high = self.delaymap[event.pitch]
        except KeyError:
            low, high = self.delaymap[midi.G_3]
        delay_in_sec = low + (high - low) * (velocity / float(0x7f))
        msdelay = delay_in_sec * 1000
        return int(msdelay)

    def calculate_tickdelay(self, pitch, velocity, tick, stream):
        tempoev = stream.tempomap.get_tempo(tick)
        tempo = tempoev.tempo
        resolution = stream.resolution
        try:
            low, high = self.delaymap[pitch]
        except KeyError:
            low, high = self.delaymap[midi.G_3]
        delay_in_sec = low + (high - low) * (velocity / float(0x7f))
        beats_per_sec = tempo / 60.0
        beats_per_delay = beats_per_sec * delay_in_sec
        ticks_per_delay = int(beats_per_delay * resolution)
        return int(ticks_per_delay)

    ## Lowlevel
    def translate_solenoid(self, pitch):
        return self.solenoid_map[pitch]

    def translate_led(self, pitch):
        return self.led_map[pitch]

    def light_on(self, pitch, tick):
        velocity = 0x7f
        lpitch = self.translate_led(pitch)
        noteon = self._new_note_on(lpitch, velocity, tick, channel=self.LEDChannel)
        return noteon

    def light_off(self, pitch, tick):
        lpitch = self.translate_led(pitch)
        noteoff = self._new_note_off(lpitch, tick, channel=self.LEDChannel)
        return noteoff

    def note_on(self, pitch, tick):
        velocity = 0x7f
        spitch = self.translate_solenoid(pitch)
        noteon = self._new_note_on(spitch, velocity, tick, channel=self.SolenoidChannel)
        return noteon

    def note_off(self, pitch, tick):
        spitch = self.translate_solenoid(pitch)
        noteoff = self._new_note_off(spitch, tick, channel=self.SolenoidChannel)
        return noteoff

    ## Midlevel
    def light_pair(self, pitch, tickon, tickoff, stream):
        noteon = self.light_on(pitch, tickon)
        stream.add_event(noteon)
        noteoff = self.light_off(pitch, tickoff)
        stream.add_event(noteoff)

    def note_pair(self, pitch, velocity, tick, stream):
        noteon = self.note_on(pitch, tick)
        stream.add_event(noteon)
        tdelay = self.calculate_tickdelay(pitch, velocity, tick, stream)
        noteoff = self.note_off(pitch, tick + tdelay)
        stream.add_event(noteoff)

    ## Highlevel
    def note(self, pitch, velocity, tick, stream):
        ldelay = self.calculate_tickdelay(pitch, velocity, tick, stream)
        self.light_pair(pitch, tick, tick+ldelay, stream)
        self.note_pair(pitch, velocity, tick, stream)

    def convert_track(self, stream, root=midi.G_3):
        transpose = midi.G_3 - root
        track = stream.get_current_track()
        tracknum = stream.get_current_track_number()
        stream.delete_track_by_number(tracknum)
        stream.add_track()
        for event in track:
            if isinstance(event, midi.NoteOffEvent):
                # filter out note offs
                continue
            if isinstance(event, midi.NoteOnEvent):
                # same as note off
                if event.velocity == 0:
                    continue
                pitch = event.pitch + transpose
                if pitch < midi.G_3 or pitch > midi.G_5:
                    print "Warning: Note outside heliphonic range: %s" % event
                    continue
                self.note(pitch, event.velocity, event.tick, stream)
            else:
                stream.add_event(event)

def build_lightseqs(tempo=120, resolution=100):
    LIGHT_CRAWL = []
    LIGHT_FLASH = []
    lightseqs = {
        'flash':LIGHT_FLASH,
        'crawl':LIGHT_CRAWL
    }

    heliphon = Heliphon()

    # crawl 1
    for x in [4, 8, 16]:
        crawl = midi.new_stream()
        tempo = crawl.get_tempo()
        tickinc = (crawl.resolution / x)
        tick = 0
        low = midi.G_3
        high = midi.G_5
        toggle = True
        for light in range(midi.G_3, midi.G_5 + 1):
            if toggle:
                heliphon.light_pair(low, tick, tick+tickinc, crawl)
                low += 1
            else:
                heliphon.light_pair(high, tick, tick+tickinc, crawl)
                high -= 1
            toggle = not toggle
            tick += tickinc
        LIGHT_CRAWL.append(crawl)

    # crawl 2
    for x in [16, 32, 64]:
        crawl = midi.new_stream()
        tempo = crawl.get_tempo()
        tickinc = (crawl.resolution / x)
        tick = 0
        low = midi.G_3
        high = midi.G_5
        toggle = True
        duration = (midi.G_5-midi.G_3) * tickinc
        for light in range(midi.G_3, midi.G_5 + 1):
            if toggle:
                heliphon.light_pair(low, tick, tick+duration, crawl)
                low += 1
            else:
                heliphon.light_pair(high, tick, tick+duration, crawl)
                high -= 1
            toggle = not toggle
            tick += tickinc
            duration -= tickinc
        LIGHT_CRAWL.append(crawl)

    # crawl 3
    for x in [4, 8, 16]:
        crawl = midi.new_stream()
        tempo = crawl.get_tempo()
        tickinc = (crawl.resolution / x)
        tick = 0
        toggle = True
        for light in range(midi.G_3, midi.G_5 + 1):
            heliphon.light_pair(light, tick, tick+tickinc, crawl)
            tick += tickinc
        LIGHT_CRAWL.append(crawl)

    # crawl 4
    for x in [16, 32]:
        crawl = midi.new_stream()
        tempo = crawl.get_tempo()
        tickinc = (crawl.resolution / x)
        tick = 0
        toggle = True
        duration = (midi.G_5-midi.G_3) * tickinc
        for light in range(midi.G_3, midi.G_5 + 1):
            heliphon.light_pair(light, tick, tick+duration, crawl)
            tick += tickinc
        LIGHT_CRAWL.append(crawl)

    # random
    for x in [16, 32]:
        crawl = midi.new_stream()
        tempo = crawl.get_tempo()
        tickinc = (crawl.resolution / x)
        tick = 0
        toggle = True
        duration = (midi.G_5-midi.G_3) * tickinc
        set = range(midi.G_3, midi.G_5)
        while set:
            light = random.choice(set)
            del set[set.index(light)]
            heliphon.light_pair(light, tick, tick+duration, crawl)
            tick += tickinc
        LIGHT_CRAWL.append(crawl)

    # flash
    flash = midi.new_stream()
    tickinc = flash.resolution / 4
    tick = 0
    for x in range(3):
        for light in range(midi.G_3, midi.G_5):
            heliphon.light_pair(light, tick, tick+tickinc, flash)
        tick += tickinc
    LIGHT_FLASH.append(flash)
    
    return lightseqs
